import os
import sys
from limber import componentparser
from limber import abilities as limb_abilities


class ComponentFinderError(Exception):
    '''Exception raised by the ComponentFinder.'''

    pass


class ComponentFinder(object):
    '''Find component documents in directories.
    
    This class also provide some high-level methods
    to load and parse the components file in a directory
    and methods to load components given a `ComponentInfos`
    instance.
    '''
    
    def __init__(self, userdir, systemdir):

        self._userdir = userdir
        self._systemdir = systemdir
        self._parser = componentparser.ComponentParser()

    def get_paths(self, userdir=None, systemdir=None):
        '''Return a tuple of the paths to the components directories.

        The tuple returned is of the form:

            user-path, system-path
        '''

        if userdir is None:
            userdir = self._userdir
        if systemdir is None:
            systemdir = self._systemdir

        sys_components = os.path.join(systemdir, 'Components')
        usr_components = os.path.join(userdir, 'Components')
        return usr_components, sys_components

    def find_components(self, directory):
        '''Generator that yield components classes found in *directory*.'''

        for infos in self.find_components_infos(directory):
            yield self.load_component(infos)

    def find_components_infos(self, directory):
        '''Generator that yield *ComponentInfos* instances.'''

        for fname in os.listdir(directory):
            if not fname.endswith('.component'):
                continue
            component_file = open(os.path.join(directory, fname))
            yield self._parser.parse(component_file)

    def find_components_and_abilities(self, directory):
        '''Generator that yield tuples of component, abilities.'''

        for infos in self.find_components_infos(directory):
            abilities = self.get_abilities(infos.abilities)
            component = self.load_component(infos)
            yield component, abilities

    def load_component(self, infos):
        '''Return the component obtained from the given infos.'''

        if isinstance(infos.directory, basestring):
            dirs = [infos.directory]
        else:
            dirs = infos.directory

        if isinstance(infos.filename, basestring):
            fnames = [infos.filename]
        else:
            fnames = infos.filenames

        component_class = None
        for dir in dirs:
            if not os.path.exists(os.path.join(dir)):
                continue

            sys.path.insert(0, dir)
            for fname in fnames:
                if not os.path.exists(os.path.join(dir,fname)):
                    continue
                if '.'in fname:
                    fname = fname[:fname.rindex('.')]


                mod = __import__(fname)
                try:
                    component_class = getattr(mod, infos.class_name)
                    break
                except AttributeError:
                    pass
            else:
                sys.path.pop(0)
                continue
            break
        if component_class is None:
            raise ComponentFinderError('Could not load the component.')
        return component_class

    def get_abilities(self, abilities):
        '''Return a list of abilities.'''

        if isinstance(abilities, basestring):
            abilities = [abilities]

        real_abilities = []
        for ability in abilities:
            cleaned = ability.replace('_','')
            if not cleaned.isupper():
                raise ComponentFinderError('Invalid ability name: %r' % ability)
            try:
                real_abilities.append(limb_abilities.ABILITIES[ability])
            except KeyError:
                real_abilities.append(limb_abilities.register_ability(ability))
        return tuple(real_abilities)
